<?php
class Hotp
{
    protected $algo;

    public function __construct($algo = 'sha1')
    {
        $this->algo = $algo;
    }

    public function GenerateToken($key, $count = 0, $length = 6)
    {
        $count = $this->packCounter($count);
        $hash = hash_hmac($this->algo, $count, $key);
        $code = $this->genHTOPValue($hash, $length);

        $code = str_pad($code, $length, "0", STR_PAD_LEFT);
        $code = substr($code, (-1 * $length));

        return $code;
    }

    private function packCounter($counter)
    {
        // the counter value can be more than one byte long,
        // so we need to pack it down properly.
        $cur_counter = array(0, 0, 0, 0, 0, 0, 0, 0);
        for ($i = 7; $i >= 0; $i--) {
            $cur_counter[$i] = pack('C*', $counter);
            $counter = $counter >> 8;
        }

        $bin_counter = implode($cur_counter);

        // Pad to 8 chars
        if (strlen($bin_counter) < 8) {
            $bin_counter = str_repeat(chr(0), 8 - strlen($bin_counter)) . $bin_counter;
        }

        return $bin_counter;
    }

    private function genHTOPValue($hash, $length)
    {
        // store calculate decimal
        $hmac_result = [];

        // Convert to decimal
        foreach (str_split($hash, 2) as $hex) {
            $hmac_result[] = hexdec($hex);
        }

        $offset = (int)$hmac_result[count($hmac_result)-1] & 0xf;

        $code = (int)($hmac_result[$offset] & 0x7f) << 24
            | ($hmac_result[$offset+1] & 0xff) << 16
            | ($hmac_result[$offset+2] & 0xff) << 8
            | ($hmac_result[$offset+3] & 0xff);

        return $code % pow(10, $length);
    }

    public static function GenerateSecret($length = 16)
    {
        if ($length % 8 != 0) {
            throw new \Exception("Length must be a multiple of 8");
        }

        $secret = openssl_random_pseudo_bytes($length, $strong);
        if (!$strong) {
            throw new \Exception("Random string generation was not strong");
        }

        return $secret;
    }
}

class Totp extends Hotp
{
    private $startTime;
    private $timeInterval;

    public function __construct($algo = 'sha1', $start = 0, $ti = 30)
    {
        parent::__construct($algo);
        $this->startTime = $start;
        $this->timeInterval = $ti;
    }

    public function GenerateToken($key, $time = null, $length = 6)
    {
        // Pad the key if necessary
        if ($this->algo === 'sha256') {
            $key = $key . substr($key, 0, 12);
        } elseif ($this->algo === 'sha512') {
            $key = $key . $key . $key . substr($key, 0, 4);
        }

        // Get the current unix timestamp if one isn't given
        if (is_null($time)) {
            $time = (new \DateTime())->getTimestamp();
        }

        // Calculate the count
        $now = $time - $this->startTime;
        $count = floor($now / $this->timeInterval);

        // Generate a normal HOTP token
        return parent::GenerateToken($key, $count, $length);
    }
}

function randPwdaa($length = 8 ){
    $chars = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
        'i', 'j', 'k', 'l','m', 'n', 'o', 'p', 'q', 'r', 's',
        't', 'u', 'v', 'w', 'x', 'y','z', 'A', 'B', 'C', 'D',
        'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L','M', 'N', 'O',
        'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y','Z',
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9','@','#',
        '!','$');
    // 在 $chars 中随机取 $length 个数组元素键名
    $keys = array_rand($chars, $length);
    $pass_id = '';
    for($i = 0; $i < $length; $i++)
    {
        // 将 $length 个数组元素连接成字符串
        $pass_id .= $chars[$keys[$i]];
    }
    return $pass_id;
}

$t = new Totp();
echo $t->GenerateToken(randPwdaa().time());


